#
# LuckyBot4, a python IRC bot
# (c) Copyright 2008 by Lucas van Dijk
# http://www.return1.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
#
# $Id: __init__.py 71 2009-05-08 13:32:15Z luckyluke56 $
#

try:
	from xml.etree import ElementTree
except ImportError:
	try:
		from elementtree import ElementTree
	except ImportError:
		raise

from luckybot import path
from luckybot.bot.plugins import Plugin
from luckybot.luckynet.protocols.irc import Format
import sqlite3 as sqlite
import re
import os
import urllib2 as urllib

class RssException(Exception):
	pass

class RssFeed(object):
	"""
		Reads RSS feeds
	"""

	def __init__(self, url):
		self.url = url

	def _get_xml(self, url):
		"""
			Gets an Element object for the given URL

			@type url: string
			@param url: The url to fetch data from

			@rtype: elementtree.Element
			@return: An Element object
		"""

		data = urllib.urlopen(url)

		try:
			tree = ElementTree.ElementTree(file=data)
		finally:
			data.close()

		if not tree or tree == None:
			raise Exception, 'Could not parse XML'

		return tree.getroot()

	def parse(self, max = 5):
		"""
			Reads the RSS feed

			@type max: int
			@param max: Maximum number of items to read
		"""

		xml = self._get_xml(self.url)
		self.title = xml.find('channel').find('title').text

		self.items = []

		items = xml.find('channel').findall('item')

		i = 0
		while i < min(max, len(items)):
			try:
				description = items[i].find('description').text
			except:
				description = ''

			try:
				link = items[i].find('link').text
			except:
				link = ''

			if items[i].find('title').text.startswith('ADV:'):
				max += 1
				i += 1
				continue

			item = {
				'title': items[i].find('title').text,
				'description': description,
				'link': link
			}

			self.items.append(item)
			i += 1

	def __iter__(self):
		return iter(self.items)

class RssPlugin(Plugin):
	"""
		Class which handles the plugin commands etc
	"""

	PLUGIN_INFO = {
		'name': 'RSS Reader',
		'description': 'Reads given rss feeds, and displays the last items',
		'authors': ['Lucas van Dijk'],
		'version': '1.0',
		'website': 'http://www.return1.net'
	}

	def initialize(self):
		"""
			Plugin initializer

			Registers all plugin commands
		"""

		# Register commands
		self.register_command('rss', self.read_feed, help=self.lang.get('rss_help'), args="name|url")
		self.register_command('last', self.read_feed, help=self.lang.get('rss_last_help'), args="name|url")
		self.register_command('addfeed', self.add_feed, help=self.lang.get('addfeed_help'), args="name url")
		self.register_command('reviewfeed', self.review_feeds, help=self.lang.get('reviewfeed_help'), args="[id [yes|no]]")
		self.register_command('feeds', self.list_feeds, help=self.lang.get('feeds_help'))

		# Setup database connection
		self.connection = sqlite.connect(path.get_personal_file('rss.db'))
		self.connection.isolation_level = None
		self.connection.row_factory = sqlite.Row

		sql = """
		CREATE TABLE IF NOT EXISTS feeds (
			id integer NOT NULL PRIMARY KEY,
			name varchar(50),
			url varchar(255),
			moderated integer(1) default 0
		);
		"""

		self.connection.execute(sql)

	def _validate_url(self, url):
		"""
			Checks if the given url is a valid url

			@type url: string
			@param url: The url to validate

			@rtype: bool
			@return: True if its a valid url
		"""

		regexp = re.compile('([\w]+?://[\w\#$%&~/.\-;:=,?@\[\]+]*)$')
		return regexp.match(url) != None

	def read_feed(self, event):
		"""
			Called when the rss command is used.
			Reads a given url, or selects the url from database,
			and shows the latest items in the feed
		"""

		try:
			url = Format.remove(event.message.bot_args)

			# Check URL
			if not self._validate_url(url):
				url = self.get_url(url)
				if url == None:
					raise RssException, self.lang.get('not_found')

			rss = RssFeed(url)

			if event.message.bot_command == 'rss':
				rss.parse(5)
			else:
				rss.parse(1)

			event.channel.send_pm(Format.color('darkblue') + Format.bold() + rss.title)
			for item in rss:
				vars = {
					'title': item['title'],
					'url': item['link']
				}

				event.channel.send_pm(self.lang.get('item_template', vars))
		except RssException, error:
			event.channel.send_pm(error)

	def list_feeds(self, event):
		"""
			Called when feeds command is used
			Lists all available feeds
		"""

		sql = """
		SELECT *
		FROM feeds
		WHERE moderated = 1
		ORDER BY name ASC
		"""

		cursor = self.connection.execute(sql)

		buffer = "%s%s%s:%s " % (Format.color('darkblue'), Format.bold(), self.lang.get('available_feeds'), Format.normal())
		for row in cursor:
			buffer += row['name'] + ", "

			if len(buffer) > 255:
				event.channel.send_pm(buffer[0:-2])
				buffer = ""

		if buffer:
			event.channel.send_pm(buffer[0:-2])

	def get_url(self, name):
		"""
			Retreives the url from database for a given feed name

			@type name: string
			@param name: The feed name

			@rtype: None|string
			@return: None if the feed couldn't be found, or else the feed url
		"""
		cursor = self.connection.execute('SELECT url FROM feeds WHERE name = ? AND moderated = 1 LIMIT 1', (name,))

		row = cursor.fetchone()

		return None if row == None else row[0]

	def add_feed(self, event):
		"""
			Called when !addfeed is used

			This command adds a feed into database, which has to be
			reviewed first before it can be used.
		"""

		try:
			args = event.message.bot_args.split(' ', 1)

			# Check for enough arguments
			if len(args) == 1:
				raise RssException, self.lang.get('addfeed_syntax').replace('!', self.bot.settings.get('Bot', 'command_prefix'))

			url = Format.remove(args[1])

			# Check URL
			if not self._validate_url(url):
				raise RssException, self.lang.get('invalid_url')

			name = Format.remove(args[0])

			regexp = re.compile('^[a-zA-Z0-9\-_.]+$')
			if not regexp.match(name):
				raise RssException, self.lang.get('invalid_name')

			cursor = self.connection.cursor()
			self.connection.execute('INSERT INTO feeds (name, url, moderated) VALUES (?, ?, ?)', (name, url, 1 if event.user.check_logged_in() else 0))

			event.user.send_notice(self.lang.get('feed_added'))
		except RssException, error:
			event.channel.send_pm(error)

	def review_feeds(self, event):
		"""
			Called when !reviewfeed command is used

			With this command, bot admins can review added feeds. They
			can view the current unreviewed feeds and allow or deny an
			added feed,
		"""

		if not event.user.check_logged_in():
			event.user.send_notice(self.lang.get('access_denied'))
			return

		args = event.message.bot_args.split()

		if len(args) == 0:
			# Display a list of unreviewed feeds
			cursor = self.connection.execute('SELECT * FROM feeds WHERE moderated = 0 ORDER BY id ASC')

			event.user.send_notice( \
				Format.color('darkblue') + Format.bold() +  \
				self.lang.get('unreviewed_feeds'))
			for row in cursor:
				event.user.send_notice(\
					"%s%s:%s %d %s%s:%s %s" % (Format.bold(),
						self.lang.get('id'), Format.bold(), int(row[0]),
						Format.bold(), self.lang.get('url'),
						Format.bold(), row[2]
					)
				)
		elif len(args) == 2:
			# Check for valid feed ID
			if not args[0].isdigit():
				event.user.send_notice(self.lang.get('invalid_id'))
				return

			# Review a feed
			if args[1] in ['ok', 'good', 'yes']:
				self.connection.execute('UPDATE feeds SET moderated = 1 WHERE id = ?', (args[0],))
				event.user.send_notice(self.lang.get('feed_reviewed'))
			elif args[1] in ['wrong', 'bad', 'no']:
				self.connection.execute('DELETE FROM feeds WHERE id = ?', (args[0],))
				event.user.send_notice(self.lang.get('feed_reviewed'))
			else:
				event.user.send_notice(self.lang.get('review_syntax').replace('!', self.bot.settings.get('Bot', 'command_prefix')))
		else:
			event.user.send_notice(self.lang.get('review_syntax').replace('!', self.bot.settings.get('Bot', 'command_prefix')))

	def destroy(self):
		self.connection.close()

